/**
   @file memory.c
   @brief The memory workload.
 */

#include <stdlib.h>
#include "cJSON.h"
#include "random.h"
#include "memory.h"
#include "scheduler.h"
#include "generator.h"

/** The chunk of memory to perform IO on. */
unsigned char *memory_buffer;

/** The end of the section of memory to perform IO on, used as a guard against reading from and writing to memory outside the buffer. */
unsigned char *memory_out_of_bounds;


mode *setup_memory_read_random(cJSON *);
mode *setup_memory_write_random(cJSON *);
mode *setup_memory_read_sequential(cJSON *);
mode *setup_memory_write_sequential(cJSON *);

/** Read 1 byte at random positions in memory n times where n is a random variable described by the mode struct. */
void *memory_read_rand(void *);

/** Write 1 byte at random positions in memory n times where n is a random variable described by the mode struct. */
void *memory_memory_write_rand(void *);

/** Read some bytes in sequence starting at a random position in memory. */
void *memory_memory_read_sequential(void *);

/** Write some bytes in sequence starting at a random position in memory. */
void *memory_write_sequential(void *);

/** Initialize memory_buffer from a JSON object. */
void setup_memory_block(cJSON *);

/** Hash table to store supported modes for the memory workload. */
mode_definition_list *memory_modes[HASHSIZE];

/** Spawn a memory job and schedule the next arrival. */
void create_memory_workload(void *);

/**
   @param memory The chunk of memory to read from
   @param out_of_bounds Pointer to the first place in memory that is outside the range of the memory to read from.
   @param position The position in memory to start reading from.
   @param bytes The number of bytes to read from memory.
 */
int memory_read_bytes(unsigned char *memory, unsigned char *out_of_bounds, unsigned long position, unsigned int bytes) {
  int i = 0;
  unsigned char *p = &memory[position];
  for(i, p; i < bytes; i++, p++) {
    if (p == out_of_bounds) {p = memory;}
    *p; /* Read the value of this byte*/
  }
  return 1;
}

/** 
    @param memory The chunk of memory to write to
    @param out_of_bounds Pointer to the first place in memory that is outside the range of the memory to write to. 
    @param position The position in memory to start reading from.
    @param bytes The number of bytes to read from memory. 
 */
int memory_write_bytes(unsigned char *memory, unsigned char *out_of_bounds, unsigned long position, unsigned int bytes) {
  unsigned int i = 0;
  unsigned char *p = &memory[position];
  for(i, p; i < bytes; i++, p++) {
    if(p == out_of_bounds) {p = memory;}
    *p |= 0xFF; /* Write to this byte */
  }
  return 1;
}

resource_model *setup_memory(cJSON *json) {
  install_mode(memory_modes, "rr", setup_memory_read_random);
  install_mode(memory_modes, "wr", setup_memory_write_random);
  install_mode(memory_modes, "rs", setup_memory_read_sequential);
  install_mode(memory_modes, "ws", setup_memory_write_sequential);
  //Setup the interarrival rate
  cJSON *distribution = cJSON_GetObjectItem(json,"distribution");
  setup_rng("memory_arrival", distribution);
  setup_memory_block(json);
  resource_model *model = malloc(sizeof(resource_model));
  model->birth = create_memory_workload;
  model->state = NULL;
  model->modes = initialize_modes(memory_modes, json);
  model->cnt_modes = count_modes(json);
  return model;
}

void setup_memory_block(cJSON *json) {
  cJSON *memory_size = cJSON_GetObjectItem(json, "memory_size");
  unsigned int i_memory_size = memory_size->valueint*1000;
  memory_buffer = malloc(i_memory_size);
  char **uniform = malloc(sizeof(char *));
  asprintf(uniform, "{\"type\":\"uniform\",\"min\":0,\"max\":%lu}", i_memory_size);
  setup_rng("random_position_in_memory",cJSON_Parse(*uniform));
  memory_out_of_bounds = &memory_buffer[i_memory_size];
}

void create_memory_workload(void *args) {
  resource_model *model = (resource_model *)args;
  double next_arrival_ms = get_random_number("memory_arrival");
  event *next_birth = malloc(sizeof(event));
  if(next_birth == NULL) {
    exit(1);
  }
  mode *m = select_mode(model);
  next_birth->timestamp = ms_from_now(next_arrival_ms);
  next_birth->job = create_memory_workload;
  next_birth->args = args;
  next_birth->next = NULL;
  pthread_t *thread = malloc(sizeof(pthread_t));
  if(thread == NULL) {
    exit(1);
  }
  pthread_create(thread, NULL,m->function, model->state);
  pthread_detach(*thread);
  schedule_event(next_birth);
}

void *memory_read_rand(void *args) {
  unsigned int bytes_to_read =(unsigned int)get_random_number("memory_rr_length");
  unsigned long position = 0;
  int i = 0;
  for(i; i < bytes_to_read; i++) {
    position = (unsigned long) get_random_number("random_position_in_memory");
    memory_read_bytes(memory_buffer, memory_out_of_bounds, position, 1);
  }
}

void *memory_write_rand(void *args) {
  unsigned int bytes_to_write = (unsigned int) get_random_number("memory_wr_length");
  unsigned long position = 0;
  int i = 0;
  for(i; i < bytes_to_write; i++) {
    position = (unsigned long) get_random_number("random_position_in_memory");
    memory_write_bytes(memory_buffer, memory_out_of_bounds, position, 1);
  }
}

void *memory_read_sequential(void *args) {
  unsigned int bytes_to_read = (unsigned int) get_random_number("memory_rs_length");
  unsigned long position = (unsigned long ) get_random_number("random_position_in_memory");
  memory_read_bytes(memory_buffer, memory_out_of_bounds, position, bytes_to_read);
}

void *memory_write_sequential(void *args) {
  unsigned int bytes_to_write = (unsigned int) get_random_number("memory_ws_length");
  unsigned long position = (unsigned long) get_random_number("random_position_in_memory");
  memory_write_bytes(memory_buffer, memory_out_of_bounds, position, bytes_to_write);
}

mode *setup_memory_read_random(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("memory_rr_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "rr";
  m->function = memory_read_rand;
  m->probability = json_probability->valuedouble;
  return m;
}

mode *setup_memory_write_random(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("memory_wr_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "wr";
  m->function = memory_write_rand;
  m->probability = json_probability->valuedouble;
  return m;
}

mode *setup_memory_read_sequential(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("memory_rs_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "rs";
  m->function = memory_read_sequential;
  m->probability = json_probability->valuedouble;
  return m;
}

mode *setup_memory_write_sequential(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("memory_ws_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "ws";
  m->function = memory_write_sequential;
  m->probability = json_probability->valuedouble;
  return m;
}
